﻿using Demo.Framework.Business;
using Demo.Framework.GraphicsMaterials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Demo.Framework
{
    public class BasicModel
    {
        public Model Model { get; protected set; }
        public Material Material { get; set; }

        protected Matrix World = Matrix.Identity;
        private Matrix _translations = Matrix.Identity;
        private Matrix _transformBoneMatrix = Matrix.Identity;
        private Matrix _rotation = Matrix.Identity;

        private readonly CBoundingBox _bBox;
        private readonly GraphicsDevice _graphics;
        public bool player = false;

        public BasicModel(Model model, GraphicsDevice graphics)
        {
            Model = model;
            _graphics = graphics;
            _bBox = new CBoundingBox(_graphics);
            _bBox.CreateBBox(Model, Matrix.CreateTranslation(new Vector3(0, 0, 0)));
            Material = new Material();
            GenerateTags();
        }

        public virtual void Update()
        {

        }

        public void Draw()
        {
            var transforms = new Matrix[Model.Bones.Count];
            Model.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in Model.Meshes)
            {
                Matrix localWorld;
                if (player)
                {
                    localWorld = GetWorld() * mesh.ParentBone.Transform * Matrix.CreateRotationY(MathHelper.ToRadians(180)) *_rotation  * _translations;
                }
                else
                {
                    localWorld = GetWorld() * mesh.ParentBone.Transform * _rotation * _translations;
                }

                Matrix view = GameEngine.Instance.Camera.View;
                Matrix projection = GameEngine.Instance.Camera.Projection;
                _transformBoneMatrix = localWorld;

                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    Effect effect = meshPart.Effect;
                    if (effect is BasicEffect)
                    {
                        ((BasicEffect) effect).World = localWorld;
                        ((BasicEffect) effect).View = view;
                        ((BasicEffect) effect).Projection = projection;
                        ((BasicEffect) effect).EnableDefaultLighting();
                    }
                    else
                    {
                        setEffectParameter(effect, "World", localWorld);
                        setEffectParameter(effect, "View", view);
                        setEffectParameter(effect, "Projection", projection);
                        setEffectParameter(effect, "CameraPosition", GameEngine.Instance.Camera.Position);
                    }

                    Material.SetEffectParameters(effect);
                }

                /*foreach (BasicEffect meshPart in mesh.Effects)
                {
                    be.EnableDefaultLighting();
                    be.Projection = GameEngine.Instance.Camera.Projection;
                    be.View = GameEngine.Instance.Camera.View;
                    be.World = GetWorld() * mesh.ParentBone.Transform * _translations;

                    Effect effect = meshPart.Effect;
                    if (effect is BasicEffect)
                    {
                        ((BasicEffect)effect).World = localWorld;
                        ((BasicEffect)effect).View = View;
                        ((BasicEffect)effect).Projection = Projection;
                        ((BasicEffect)effect).EnableDefaultLighting();
                    }
                    else
                    {
                        setEffectParameter(effect, "World", localWorld);
                        setEffectParameter(effect, "View", View);
                        setEffectParameter(effect, "Projection", Projection);
                        setEffectParameter(effect, "CameraPosition", CameraPosition);
                    }

                    _transformBoneMatrix = GetWorld() * mesh.ParentBone.Transform * _translations;
                }*/


                mesh.Draw();
            }

            //_bBox.Draw(_transformBoneMatrix);
        }

        public virtual Matrix GetWorld()
        {
            return World;
        }

        public void SetWorld(Matrix world)
        {
            World = world;
            _bBox.SetWorld(world);
        }

        public void Translate(Vector3 translate)
        {
            _translations.Translation = translate;
            _bBox.SetTranslation(translate);
        }

        public void SetPosition(Vector3 translate, Matrix rotation)
        {
            _rotation = rotation;
            _translations.Translation = translate;
            _bBox.SetTranslation(translate);
        }

        public void Rotation(Matrix rotation)
        {
            _rotation = rotation;
        }

        private void GenerateTags()
        {
            foreach (ModelMesh mesh in Model.Meshes)
                foreach (ModelMeshPart part in mesh.MeshParts)
                    if (part.Effect is BasicEffect)
                    {
                        var effect = (BasicEffect)part.Effect;
                        var tag = new MeshTag(effect.DiffuseColor, effect.Texture,
                          effect.SpecularPower);
                        part.Tag = tag;
                    }
        }

        // Сохранение ссылки на эффект
        public void CacheEffects()
        {
            foreach (ModelMesh mesh in Model.Meshes)
                foreach (ModelMeshPart part in mesh.MeshParts)
                    ((MeshTag)part.Tag).CachedEffect = part.Effect;
        }
        // Восстановление эффекта
        public void RestoreEffects()
        {
            foreach (ModelMesh mesh in Model.Meshes)
                foreach (ModelMeshPart part in mesh.MeshParts)
                    if (((MeshTag)part.Tag).CachedEffect != null)
                        part.Effect = ((MeshTag)part.Tag).CachedEffect;
        }

        public void SetModelEffect(Effect effect, bool copyEffect)
        {
            foreach (ModelMesh mesh in Model.Meshes)
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    var toSet = effect;
                    // Если необходимо, копируем эффект
                    if (copyEffect)
                        toSet = effect.Clone();
                    var tag = ((MeshTag)part.Tag);
                    // Если ModelMeshPart имеет текстуру, назначаем ее эффекту
                    if (tag.Texture != null)
                    {
                        setEffectParameter(toSet, "BasicTexture", tag.Texture);
                        setEffectParameter(toSet, "TextureEnabled", true);

                    }
                    else
                        setEffectParameter(toSet, "TextureEnabled", false);
                    // Устанавливаем остальные параметры эффекта
                    setEffectParameter(toSet, "DiffuseColor", tag.Color);
                    setEffectParameter(toSet, "SpecularPower", tag.SpecularPower);
                    part.Effect = toSet;
                }
        }
        // Устанавливаем параметр одного эффекта для другого эффекта, 
        // если у него есть этот параметр
        private void setEffectParameter(Effect effect, string paramName, object val)
        {
            if (effect.Parameters[paramName] == null)
                return;
            if (val is Vector3)
                effect.Parameters[paramName].SetValue((Vector3)val);
            else if (val is bool)
                effect.Parameters[paramName].SetValue((bool)val);
            else if (val is Matrix)
                effect.Parameters[paramName].SetValue((Matrix)val);
            else if (val is Texture2D)
                effect.Parameters[paramName].SetValue((Texture2D)val);
        }

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Model = null;
        }

        #endregion
    }
}
